// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#include <stdlib.h>
#include <string.h>

#include "check.h"
#include "hashmap.h"
#include "http.h"
#include "multipart.h"
#include "oauth2.h"


struct OAuth2 *newOAuth2(const char *url, const char *clientId,
                         const char *clientSecret, const char *authCode,
                         const char *refreshToken) {
  struct OAuth2 *oauth2;
  check(oauth2 = malloc(sizeof(struct OAuth2)));
  initOAuth2(oauth2, url, clientId, clientSecret, authCode, refreshToken);
  return oauth2;
}

void initOAuth2(struct OAuth2 *oauth2, const char *url, const char *clientId,
                const char *clientSecret, const char *authCode,
                const char *refreshToken) {
  memset(oauth2, 0, sizeof(struct OAuth2));
  check(oauth2->url = strdup(url));
  check(oauth2->clientId = strdup(clientId));
  check(oauth2->clientSecret = strdup(clientSecret));

  if (authCode) {
    // This was the initial registration of the printer.
    check(!refreshToken);
    refreshOAuth2(oauth2, authCode);
  } else {
    // This is a subsequent attempt at connecting to a pre-existing printer.
    check(refreshToken);
    check(oauth2->refreshToken = strdup(refreshToken));
    refreshOAuth2(oauth2, NULL);
  }
}

void destroyOAuth2(struct OAuth2 *oauth2) {
  if (oauth2) {
    free(oauth2->url);
    free(oauth2->clientId);
    free(oauth2->clientSecret);
    free(oauth2->refreshToken);
    free(oauth2->accessToken);
  }
}

void deleteOAuth2(struct OAuth2 *oauth2) {
  destroyOAuth2(oauth2);
  free(oauth2);
}

void refreshOAuth2(struct OAuth2 *oauth2, const char *authCode) {
  // We can send both the initial request for a refresh token, and subsequent
  // requests for refreshing an access token.
  struct MultiPart multipart;
  initMultiPart(&multipart);
  if (authCode) {
    addToMultiPart(&multipart, "code", authCode,
                   strlen(authCode), "text/plain");
    addToMultiPart(&multipart, "grant_type", "authorization_code",
                   strlen("authorization_code"), "text/plain");
    addToMultiPart(&multipart, "redirect_uri", "oob", 3, "text/plain");
  } else {
    check(oauth2->refreshToken);
    addToMultiPart(&multipart, "refresh_token", oauth2->refreshToken,
                   strlen(oauth2->refreshToken), "text/plain");
    addToMultiPart(&multipart, "grant_type", "refresh_token",
                   strlen("refresh_token"), "text/plain");
  }
  addToMultiPart(&multipart, "client_id", oauth2->clientId,
                 strlen(oauth2->clientId), "text/plain");
  addToMultiPart(&multipart, "client_secret", oauth2->clientSecret,
                 strlen(oauth2->clientSecret), "text/plain");

  // Send the token request
  const char *body = POST(oauth2->url, NULL, &multipart, M_URLENCODED);
  destroyMultiPart(&multipart);

  // Parse response
  struct JSON *json = newJSON(body);
  free((void *)body);

  // Retrieve result from JSON object
  check(json->type == J_OBJECT);

  // refreshToken
  if (!oauth2->refreshToken) {
    struct JSON *refreshToken;
    check(refreshToken = (struct JSON *)getFromHashMap(&json->obj,
                                                       "refresh_token"));
    check(refreshToken->type == J_STRING);
    check(oauth2->refreshToken = strdup(refreshToken->str));
  }

  // expiresIn
  struct JSON *expiresIn;
  check(expiresIn = (struct JSON *)getFromHashMap(&json->obj, "expires_in"));
  check(expiresIn->type == J_NUMBER);
  oauth2->expiresAt = time(NULL) + expiresIn->number;

  // accessToken
  struct JSON *accessToken;
  free(oauth2->accessToken);
  check(accessToken = (struct JSON *)getFromHashMap(&json->obj,
                                                    "access_token"));
  check(accessToken->type == J_STRING);
  check(oauth2->accessToken = strdup(accessToken->str));
    
  deleteJSON(json);
}
